package org.meb.conquest.db;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import lombok.Setter;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.math3.util.MathArrays;
import org.codehaus.jackson.JsonProcessingException;
import org.meb.conquest.db.dao.CardDao;
import org.meb.conquest.db.dao.JpaDao;
import org.meb.conquest.db.helpers.PredicateUtils;
import org.meb.conquest.db.helpers.Processor;
import org.meb.conquest.db.model.CardBase;
import org.meb.conquest.db.model.CardLang;
import org.meb.conquest.db.model.CardSetBase;
import org.meb.conquest.db.model.CardSetLang;
import org.meb.conquest.db.model.CardType;
import org.meb.conquest.db.model.CycleBase;
import org.meb.conquest.db.model.CycleLang;
import org.meb.conquest.db.model.Deck;
import org.meb.conquest.db.model.DeckLink;
import org.meb.conquest.db.model.DeckMember;
import org.meb.conquest.db.model.DeckType;
import org.meb.conquest.db.model.DomainBase;
import org.meb.conquest.db.model.DomainLang;
import org.meb.conquest.db.model.Faction;
import org.meb.conquest.db.model.IBase;
import org.meb.conquest.db.model.ILang;
import org.meb.conquest.db.model.loc.Card;
import org.meb.conquest.db.query.CardQuery;
import org.meb.conquest.db.query.DeckQuery;
import org.meb.conquest.db.util.JpaUtils;
import org.meb.conquest.db.util.Transformers;
import org.meb.conquest.db.util.Utils;
import org.meb.conquest.json.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class DataLoader extends AbstractLoader {

	private static final Logger log = LoggerFactory.getLogger(DataLoader.class);

	protected static final String DATA_BASE;
	protected static final String JSON_BASE;
	protected static final String RAW_BASE;

	static {
		String tmpDataBase = System.getProperty("data.home");
		if (StringUtils.isBlank(tmpDataBase)) {
			throw new IllegalStateException("Data home not set");
		}
		if (tmpDataBase.trim().endsWith("/")) {
			DATA_BASE = tmpDataBase;
		} else {
			DATA_BASE = tmpDataBase + "/";
		}

		RAW_BASE = DATA_BASE + "_raw/";
		JSON_BASE = DATA_BASE + "json-de-done2/";
		if (!new File(JSON_BASE).exists()) {
			new File(JSON_BASE).mkdir();
		}
	}
	private static final String DOMAIN_FILE_NAME = JSON_BASE + "domain.json";
	private static final String CYCLE_FILE_NAME = JSON_BASE + "cycle.json";
	private static final String CARD_SET_FILE_NAME = JSON_BASE + "card_set.json";
	private static final String CARD_FILE_NAME = JSON_BASE + "card.json";
	private static final boolean PROC_DOMAIN = Boolean.valueOf(System.getProperty("data.proc.domain"));
	private static final boolean PROC_CYCLE = Boolean.valueOf(System.getProperty("data.proc.cycle"));
	private static final boolean PROC_CARD_SET = Boolean.valueOf(System.getProperty("data.proc.cardset"));
	private static final boolean PROC_CARD = Boolean.valueOf(System.getProperty("data.proc.card"));
	private static final boolean DEV_ENV = Boolean.valueOf(System.getProperty("dev.env"));

	private final Processor<CardBase> CB_FOR_CL_IMPORT_MIN = new Processor<CardBase>() {

		private String langCode = "pl";

		@Override
		public CardBase process(CardBase item) {
			CardBase base = new CardBase();
			base.setTechName(item.getTechName());
			base.setNumber(item.getNumber());
			base.setCardSetBase(new CardSetBase(item.getCardSetBase().getTechName()));

			CardLang langEn = item.getLangItems().get("en");
			langEn.setText(null);
			langEn.setFlavourText(null);
			CardLang lang = item.getLangItems().get(langCode);
			if (lang == null) {
				lang = new CardLang();
				lang.setLangCode(langCode);
				lang.setName(" ");
				if (StringUtils.isNotBlank(langEn.getTrait())) {
					lang.setTrait(" ");
				}
				if (StringUtils.isNotBlank(langEn.getKeyword())) {
					lang.setKeyword(" ");
				}
				lang.setRecordState("A");
				lang.setImageLangCode("en");
			}

			Map<String, CardLang> langItems = new HashMap<>();
			langItems.put(langEn.getLangCode(), langEn);
			langItems.put(lang.getLangCode(), lang);
			base.setLangItems(langItems);

			return base;
		}
	};

	private final Processor<CardSetBase> CSB_FOR_CSL_IMPORT_MIN = new Processor<CardSetBase>() {

		private String langCode = "de";

		@Override
		public CardSetBase process(CardSetBase item) {
			CardSetBase base = new CardSetBase();
			base.setTechName(item.getTechName());

			CardSetLang langEn = item.getLangItems().get("en");
			CardSetLang lang = item.getLangItems().get(langCode);
			if (lang == null) {
				lang = new CardSetLang();
				lang.setLangCode(langCode);
				lang.setName(" ");
				lang.setRecordState("A");
			}

			Map<String, CardSetLang> langItems = new HashMap<>();
			langItems.put(langEn.getLangCode(), langEn);
			langItems.put(lang.getLangCode(), lang);
			base.setLangItems(langItems);

			return base;
		}
	};

	private final Processor<CycleBase> CCB_FOR_CCL_IMPORT_MIN = new Processor<CycleBase>() {

		private String langCode = "de";

		@Override
		public CycleBase process(CycleBase item) {
			CycleBase base = new CycleBase();
			base.setTechName(item.getTechName());

			CycleLang langEn = item.getLangItems().get("en");
			CycleLang lang = item.getLangItems().get(langCode);
			if (lang == null) {
				lang = new CycleLang();
				lang.setLangCode(langCode);
				lang.setName(" ");
				lang.setRecordState("A");
			}

			Map<String, CycleLang> langItems = new HashMap<>();
			langItems.put(langEn.getLangCode(), langEn);
			langItems.put(lang.getLangCode(), lang);
			base.setLangItems(langItems);

			return base;
		}
	};

	private final Processor<DomainBase> DB_FOR_DL_IMPORT_MIN = new Processor<DomainBase>() {

		private String langCode = "de";

		@Override
		public DomainBase process(DomainBase item) {
			DomainBase base = new DomainBase();
			base.setDomain(item.getDomain());
			base.setValue(item.getValue());

			DomainLang langEn = item.getLangItems().get("en");
			DomainLang lang = item.getLangItems().get(langCode);
			if (lang == null) {
				lang = new DomainLang();
				lang.setLangCode(langCode);
				lang.setDescription(" ");
				lang.setRecordState("A");
			}

			Map<String, DomainLang> langItems = new HashMap<>();
			langItems.put(langEn.getLangCode(), langEn);
			langItems.put(lang.getLangCode(), lang);
			base.setLangItems(langItems);

			return base;
		}
	};

	@Setter
	private boolean langItemsOnly;
	private boolean disableProcessors;

	public static void main(String[] args) throws Exception {
		DataLoader loader = new DataLoader();
		try {
			if (args[0].equals("--import-json")) {
				loader.importDatabaseFromJson();
			} else if (args[0].equals("--export-json")) {
				loader.exportDatabaseToJson();
			} else if (args[0].equals("--import-text-ch")) {
				loader.importTextFromCardHunter();
			} else if (args[0].equals("--import-flat")) {
				loader.importDomainsFromFlatFile();
			} else if (args[0].equals("--rename")) {
				loader.rename();
			} else if (args[0].equals("--import-octgn")) {
				loader.importOctgnCardIds();
			} else if (args[0].equals("--generate-decks") && DEV_ENV) {
				loader.generateDecks(Integer.parseInt(args[1]), Long.parseLong(args[2]), Long.parseLong(args[3]));
			} else if (args[0].equals("--publish-decks") && DEV_ENV) {
				loader.publishDecks(Integer.parseInt(args[1]));
			} else if (args[0].equals("--dump-trait-info")) {
				loader.dumpTraitInfo();
			} else if (args[0].equals("--export-octgn")) {
				loader.exportOctgnCardIds();
			} else {
				throw new IllegalArgumentException("Invalid argument");
			}
		} finally {
			loader.cleanUp();
		}
	}

	private void dumpTraitInfo() {
		beginTransaction();

		Map<String, DomainBase> domainsByValue = new HashMap<String, DomainBase>();

		List<DomainBase> dbList = readDomainsFromDatabase();
		for (DomainBase db : dbList) {
			domainsByValue.put(db.getValue(), db);
		}

		StringBuilder myLog = new StringBuilder();

		String sql = "select card.id card_id, card.tech_name card_tn, crst.id crst_id, crst.tech_name crst_tn"
				+ ", card_en.trait trait_en, card_pl.trait trait_pl" + " from cqt_card card"
				+ " join cqt_card_set crst on card.crst_id = crst.id"
				+ " left outer join cqt_card_l card_en on card.id = card_en.card_id and card_en.lang_code = 'en'"
				+ " left outer join cqt_card_l card_pl on card.id = card_pl.card_id and card_pl.lang_code = 'pl'";
		List<Object[]> results = em.createNativeQuery(sql).getResultList();

		for (Object[] result : results) {
			String enTraitString = (String) result[4];
			String plTraitString = (String) result[5];
			if (StringUtils.isBlank(enTraitString) && StringUtils.isBlank(plTraitString)) {
				continue;
			}

			String[] enTraits = StringUtils.trimToEmpty(enTraitString).split("\\.");
			for (int i = 0; i < enTraits.length; i++) {
				enTraits[i] = enTraits[i].trim();
			}

			String[] plTraits = StringUtils.trimToEmpty(plTraitString).split("\\.");
			for (int i = 0; i < plTraits.length; i++) {
				plTraits[i] = plTraits[i].trim();
			}

			if (enTraits.length != plTraits.length) {
				myLog.append("mismatch (card tn: ").append(result[1]).append("): ").append(enTraitString).append(" # ")
						.append(plTraitString).append("\n");
				continue;
			}

			for (String enTrait : enTraits) {
				String value = Utils.toTechName(enTrait);
				DomainBase db = domainsByValue.get(value);
				if (db != null) {
					if (!db.getLangItems().containsKey("en")) {
						myLog.append("missing en (card tn: ").append(result[1]).append("): ").append(value)
								.append(" -> ").append(enTrait).append("\n");
					}
					if (!db.getLangItems().containsKey("pl")) {
						myLog.append("missing pl (card tn: ").append(result[1]).append("): ").append(value)
								.append("\n");
					}
				}
			}
		}

		// for (String description : descriptions) {
		// String value = Utils.toTechName(description);
		//
		// DomainBase db = new DomainBase();
		// db.setDomain("trait");
		// db.setValue(value);
		// db = dbDao.findUnique(db);
		// if (db == null) {
		// db = new DomainBase();
		// db.setDomain("trait");
		// db.setValue(value);
		// db.setRecordState("A");
		// DomainLang dl = new DomainLang();
		// dl.setDescription(description);
		// dl.setLangCode("en");
		// dl.setRecordState("A");
		// dl.setBase(db);
		// db.getLangItems().put("en", dl);
		// log.info("persisting domain: {}, {}", description, value);
		// myLog.append(description).append("\t->\t").append(value).append("\n");
		// // em.persist(db);
		// }
		// }

		log.info(myLog.toString());

		endTransaction();

	}

	// if (DEV_ENV) {
	//
	// StringBuilder keywordInfo = new StringBuilder();
	// StringBuilder keywordUpdate = new StringBuilder();
	//
	// DatabaseUtils.executeSetUserLang(em, "en");
	// List<Domain> domains = new JpaDao<Domain>(em).find(new
	// Domain("keyword"));
	// Set<String> domainSet = new HashSet<>();
	// for (Domain domain : domains) {
	// domainSet.add(domain.getDescription());
	// }
	//
	// int counter = 0;
	// Map<String, Integer> counters = new HashMap<>();
	// for (Domain domain : domains) {
	// counters.put(domain.getDescription().toLowerCase(), 0);
	// }
	//
	// List<Card> cards = new JpaDao<Card>(em).find(new Card());
	// for (Card card : cards) {
	// String text = card.getText();
	// StringBuilder keywords = new StringBuilder();
	// if (StringUtils.isNotBlank(text)) {
	// text = text.toLowerCase();
	//
	// for (Domain domain : domains) {
	// String keyword = domain.getDescription();
	//
	// Pattern p = Pattern.compile("^" + keyword.toLowerCase() + ".*$",
	// Pattern.MULTILINE);
	// if (p.matcher(text).find()) {
	// keywords.append(keyword).append(". ");
	// }
	// }
	//
	// if (!keywords.toString().contains("No Attachments.") &&
	// text.contains("attachments.")) {
	// keywords.append("No Attachments").append(". ");
	// }
	//
	// if (keywords.length() > 0) {
	// keywordInfo.append(card.getName()).append("\t->\t").append(keywords).append("\n");
	// keywordUpdate.append("update cqt_card_l set keyword = '");
	// keywordUpdate.append(keywords.toString().trim());
	// keywordUpdate.append("' where lang_code = 'en' and card_id = ");
	// keywordUpdate.append(card.getId());
	// keywordUpdate.append(" and keyword is null;\n");
	//
	// counter++;
	//
	// for (Domain domain : domains) {
	// String keyword = domain.getDescription().toLowerCase();
	// if (keywords.toString().toLowerCase().contains(keyword)) {
	// counters.put(keyword, counters.get(keyword) + 1);
	// }
	// }
	// }
	// }
	// }
	//
	// log.info("counter: {}", counter);
	// for (Domain domain : domains) {
	// String keyword = domain.getDescription().toLowerCase();
	// log.info("counter: {}, value: {}", keyword, counters.get(keyword));
	// }
	// log.info(keywordInfo.toString());
	// log.info(keywordUpdate.toString());
	// }

	private void rename() {
		String dirName = DATA_BASE + "../image/_raw_/card/pl/przemo_yt/05-ttb";
		File dir = new File(dirName);
		String[] fileNames = dir.list(new FilenameFilter() {

			@Override
			public boolean accept(File dir, String name) {
				return name.matches("[\\p{Digit}]{1,3}_2\\.((jpg)|(png))");
			}
		});

		JpaDao<Card, CardQuery> dao = new JpaDao<>(em);
		Card card = new Card();
		card.setCrstTechName("the-threat-beyond");
		List<Card> cards = dao.find(card);
		HashMap<Integer, Card> map = new HashMap<Integer, Card>();
		MapUtils.populateMap(map, cards, new Transformer<Card, Integer>() {

			@Override
			public Integer transform(Card input) {
				return input.getNumber();
			}
		});

		for (String fileName : fileNames) {
			int underscore = fileName.indexOf('_');
			// int dot = fileName.indexOf('.');
			Integer number = Integer.valueOf(fileName.substring(0, underscore));
			String newFileName = StringUtils.leftPad(number.toString(), 3, '0') + "-" + map.get(number).getTechName();
			if (fileName.endsWith("jpg")) {
				newFileName += ".jpg";
			} else if (fileName.endsWith("png")) {
				newFileName += ".png";
			}
			System.out.println(fileName + " -> " + newFileName);
			File file = new File(dir, fileName);
			file.renameTo(new File(dir, newFileName));
		}
	}

	private void renameCGDB() {
		String dirName = DATA_BASE + "../image/_raw_/card/en/ffg/05-ttb";
		File dir = new File(dirName);
		String[] fileNames = dir.list(new FilenameFilter() {

			@Override
			public boolean accept(File dir, String name) {
				return name.matches("med_WHK06_[\\p{Digit}]{1,3}.jpg");
			}
		});

		JpaDao<Card, CardQuery> dao = new JpaDao<>(em);
		Card card = new Card();
		card.setCrstTechName("the-threat-beyond");
		List<Card> cards = dao.find(card);
		HashMap<Integer, Card> map = new HashMap<Integer, Card>();
		MapUtils.populateMap(map, cards, new Transformer<Card, Integer>() {

			@Override
			public Integer transform(Card input) {
				return input.getNumber();
			}
		});

		for (String fileName : fileNames) {
			int underscore = fileName.lastIndexOf('_');
			int dot = fileName.indexOf('.');
			Integer number = Integer.valueOf(fileName.substring(underscore + 1, dot));
			String newFileName = StringUtils.leftPad(number.toString(), 3, '0') + "-" + map.get(number).getTechName();
			if (fileName.endsWith("jpg")) {
				newFileName += ".jpg";
			} else if (fileName.endsWith("png")) {
				newFileName += ".png";
			}
			System.out.println(fileName + " -> " + newFileName);
			File file = new File(dir, fileName);
			file.renameTo(new File(dir, newFileName));
		}
	}

	private void generateDecks(int deckCount, long userIdMin, long userIdMax) {

		beginTransaction();

		List<Deck> decks = new JpaDao<Deck, DeckQuery>(em).find(new Deck());
		for (Deck deck : decks) {
			em.remove(deck);
			em.flush();
		}

		int maxDeckCount = 1000;
		if (deckCount > maxDeckCount) {
			throw new RuntimeException("Are you nuts?!");
		}

		Card warlordExample = new Card();
		warlordExample.setType(CardType.WARLORD);
		JpaDao<Card, CardQuery> cardDao = new JpaDao<>(em);
		List<Card> warlords = cardDao.find(warlordExample);

		int padSize = (int) Math.floor(Math.log10(maxDeckCount));

		for (long userId = userIdMin; userId <= userIdMax; userId++) {

			for (int i = 0; i < deckCount; i++) {
				Card warlord = warlords.get(RandomUtils.nextInt(warlords.size()));

				Card cardExample = new Card();
				CardQuery cardQuery = new CardQuery(cardExample);
				cardQuery.setDeckFaction(warlord.getFaction());
				cardQuery.setDeckWarlordId(warlord.getId());
				List<Card> cards = new CardDao(em).find(cardQuery);
				Collections.shuffle(cards);

				Deck deck = new Deck();
				deck.setName(StringUtils.leftPad(String.valueOf(i), padSize, '0') + " - "
						+ RandomStringUtils.randomAlphabetic(20));
				log.info(deck.getName());
				// deck.setUserId(userId);
				Date date = new Date();
				deck.setCreateDate(date);
				deck.setModifyDate(date);
				deck.setWarlord(warlord);

				HashSet<Faction> validFactions = new HashSet<Faction>();
				validFactions.add(warlord.getFaction());
				validFactions.add(Faction.NEUTRAL);

				int totalQuantity = 0;
				for (Card card : cards) {
					if (validFactions.size() == 2) {
						validFactions.add(card.getFaction());
					} else if (!validFactions.contains(card.getFaction())) {
						continue;
					}

					double bonus = card.getFaction() == warlord.getFaction() ? 0.3 : 0.0;
					double value = RandomUtils.nextDouble() + bonus;
					int quantity = 0;

					Integer cardQuantity = (Integer) ObjectUtils.defaultIfNull(3, new Integer(3));

					boolean flagWarlord = card.getId().equals(warlord.getId());
					boolean flagSignatureSquad = card.getWarlordId() != null;
					if (flagWarlord || flagSignatureSquad) {
						quantity = 1;
					} else if (value > 0.8) {
						quantity = 3;
					} else if (value > 0.6) {
						quantity = 2;
					} else if (value > 0.5) {
						quantity = 1;
					} else {
						continue;
					}

					if (totalQuantity < 50 || flagWarlord || flagSignatureSquad) {
						quantity = Math.min(quantity, cardQuantity);
						totalQuantity += quantity;

						DeckMember deckMember = new DeckMember();
						deckMember.setQuantity(quantity);
						deckMember.setCard(card);
						deckMember.setDeck(deck);
						deck.getDeckMembers().add(deckMember);
					}
				}

				em.persist(deck);
				em.flush();
			}
		}

		endTransaction();
	}

	public void publishDecks(int deckCount) {
		beginTransaction();

		Deck example = new Deck(DeckType.SNAPSHOT);
		List<Deck> decks = new JpaDao<Deck, DeckQuery>(em).find(example);
		for (Deck deck : decks) {
			em.remove(deck);
			em.flush();
		}

		example = new Deck(DeckType.BASE);
		decks = new JpaDao<Deck, DeckQuery>(em).find(example);
		Collection<Integer> deckIds = CollectionUtils.collect(decks, new Transformer<Deck, Integer>() {

			@Override
			public Integer transform(Deck deck) {
				return deck.getId().intValue();
			}
		});

		Map<Long, Deck> decksMap = new HashMap<>();
		MapUtils.populateMap(decksMap, decks, Transformers.DECK_ID);

		int[] deckIdsArray = ArrayUtils.toPrimitive(deckIds.toArray(new Integer[deckIds.size()]));
		MathArrays.shuffle(deckIdsArray);
		int publishedCount = 0;
		for (int deckId : deckIdsArray) {
			Deck deck = decksMap.get(new Long(deckId));
			int quantity = 0;
			for (DeckMember deckMember : deck.getDeckMembers()) {
				quantity += deckMember.getQuantity();
			}
			if (quantity >= 50) {
				em.detach(deck);
				deck.setId(null);
				deck.setVersion(null);
				deck.setSnapshotBase(em.find(Deck.class, new Long(deckId)));
				deck.setSnapshotPublic(Boolean.TRUE);
				Date date = new Date();
				deck.setCreateDate(date);
				deck.setModifyDate(date);
				deck.setType(DeckType.SNAPSHOT);
				deck.setDeckLinks(new HashSet<DeckLink>());
				deck.setDeckMembers(new HashSet<DeckMember>(deck.getDeckMembers()));
				for (DeckMember deckMember : deck.getDeckMembers()) {
					deckMember.setId(null);
					deckMember.setVersion(null);
				}
				em.persist(deck);
				em.flush();

				if (++publishedCount >= deckCount) {
					break;
				}
			}
		}

		endTransaction();
	}

	public DataLoader() {

	}

	public void importTextFromCardHunter() throws Exception {
		String langCode = "pl";
		String path = DATA_BASE + "/card-hunter/" + langCode + "/v1.7/warlord-cycle.xml";

		Map<String, Element> elemsMap = new HashMap<>();

		DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document doc = db.parse(path);
		NodeList nodes = doc.getElementsByTagName("card");
		for (int i = 0; i < nodes.getLength(); i++) {
			Element elem = (Element) nodes.item(i);
			elemsMap.put(elem.getAttribute("id"), elem);
		}

		String idPrefix = "002";
		List<CardBase> cbList = readCardsFromDatabase(PredicateUtils.createCardSet(1, 2, 3, 4, 5, 6));
		for (CardBase cb : cbList) {
			CardLang cl = cb.getLangItems().get(langCode);
			if (cl == null || StringUtils.isNotBlank(cl.getText())) {
				continue;
			}

			Element elem = elemsMap.get(idPrefix + StringUtils.leftPad(Integer.toString(cb.getNumber()), 3, "0"));
			if (elem == null) {
				continue;
			}

			String text = null;
			NodeList properties = elem.getElementsByTagName("property");
			for (int i = 0; i < properties.getLength(); i++) {
				Element property = (Element) properties.item(i);
				if (property.getAttribute("name").equals("Text")) {
					text = property.getAttribute("value");
				}
			}

			if (StringUtils.isNotBlank(text)) {
				cl.setText(text.trim());
			}
		}

		writeCardsToJsonFile(cbList, null);
	}

	public void importDatabaseFromJson() throws IOException {
		langItemsOnly = true;

		beginTransaction();
		if (PROC_DOMAIN) {
			writeDomainsToDatabase();
		}
		if (PROC_CYCLE) {
			writeCyclesToDatabase();
		}
		if (PROC_CARD_SET) {
			writeCardSetsToDatabase();
		}
		if (PROC_CARD) {
			writeCardsToDatabase();
		}
		endTransaction();
	}

	public void exportDatabaseToJson() throws IOException {
		disableProcessors = true;

		if (PROC_DOMAIN) {
			// Predicate<DomainBase> keepPredicate =
			// PredicateUtils.createDomain("faction", "faction-short",
			// "card-type");
			Predicate<DomainBase> keepPredicate = null;
			List<DomainBase> dbList = readDomainsFromDatabase(keepPredicate);
			writeDomainsToJsonFile(dbList, DB_FOR_DL_IMPORT_MIN);
		}
		if (PROC_CYCLE) {
			List<CycleBase> ccbList = readCyclesFromDatabase();
			writeCyclesToJsonFile(ccbList, CCB_FOR_CCL_IMPORT_MIN);
		}
		if (PROC_CARD_SET) {
			List<CardSetBase> csbList = readCardSetsFromDatabase();
			writeCardSetsToJsonFile(csbList, CSB_FOR_CSL_IMPORT_MIN);
		}
		if (PROC_CARD) {
			// Predicate<CardBase> keepPredicate =
			// PredicateUtils.createCardSet("descendants-of-isha",
			// "the-great-devourer");
			Predicate<CardBase> keepPredicate = PredicateUtils.createCardSet(6);
			// Predicate<CardBase> keepPredicate = null;
			List<CardBase> cbList = readCardsFromDatabase(keepPredicate);
			writeCardsToJsonFile(cbList, CB_FOR_CL_IMPORT_MIN);
		}
	}

	public void importOctgnCardIds() {
		beginTransaction();

		Map<String, String> map = new HashMap<>();
		try {
			DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document doc = db.parse(DATA_BASE + "/octgn/01-warlord-cycle/set.xml");
			NodeList cardNodes = doc.getElementsByTagName("card");
			for (int i = 0; i < cardNodes.getLength(); i++) {
				Element cardElem = (Element) cardNodes.item(i);
				String id = cardElem.getAttribute("id");
				String name = cardElem.getAttribute("name");
				String techName = Utils.toTechName(name);
				System.out.println(id + " " + name + " " + techName);
				map.put(techName, id);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		Predicate<CardBase> keepPredicate = PredicateUtils.createCardSet(6);
		List<CardBase> cbList = readCardsFromDatabase(keepPredicate);
		for (CardBase cb : cbList) {
			if (cb.getOctgnId() == null && map.containsKey(cb.getTechName())) {
				cb.setOctgnId(map.get(cb.getTechName()));
				em.flush();
			}
		}

		endTransaction();
	}

	private void exportOctgnCardIds() {
		StringBuilder log = new StringBuilder();

		List<CardBase> cards = readCardsFromDatabase(PredicateUtils.createCardSet(5));
		for (CardBase cb : cards) {
			log.append(StringUtils.leftPad("" + cb.getNumber(), 3, '0'));
			log.append("|");
			log.append(cb.getLangItems().get("en").getName());
			log.append("|");
			log.append(cb.getOctgnId());
			log.append("\n");
		}

		System.out.println(log.toString());
	}

	public void importDomainsFromFlatFile() throws FileNotFoundException, IOException {

		List<String> lines = IOUtils.readLines(new FileInputStream(new File(RAW_BASE + "domain.txt")));

		// if (!globalTransaction) {
		em.getTransaction().begin();
		// }

		String domain = null;
		String[] langs = { "en", "pl" };

		Iterator<String> iter = lines.iterator();
		while (iter.hasNext()) {
			String line = iter.next();
			if (StringUtils.isBlank(line)) {
				continue;
			}

			if (line.trim().matches(":[A-Z ]+:")) {
				domain = Utils.toTechName(line.trim());
			} else {
				if (StringUtils.isBlank(domain)) {
					throw new IllegalStateException("Domain is blank");
				}

				String[] tokens = line.replaceAll("\t+", "\t").split("\\t");
				DomainBase db = new DomainBase();
				db.setDomain(domain);
				db.setRecordState("A");
				db.setValue(Utils.toTechName(tokens[0]));

				for (int i = 0; i < tokens.length; i++) {
					DomainLang dl = new DomainLang();
					dl.setBase(db);
					dl.setRecordState("A");
					dl.setLangCode(langs[i]);
					dl.setDescription(tokens[i]);
					db.getLangItems().put(langs[i], dl);
				}
				em.persist(db);
				em.flush();
			}
		}

		// if (!globalTransaction) {
		em.getTransaction().commit();
		// }
	}

	public void writeDomainsToDatabase() throws IOException {
		DomainBase[] sources = JsonUtils.read(DOMAIN_FILE_NAME, DomainBase[].class);

		for (DomainBase source : sources) {
			log.info("writing: {}/{}", source.getDomain(), source.getValue());

			DomainBase target = dbDao.findUnique(source);
			if (target == null) {
				persist(source);
			} else {
				merge(target, source);
			}
			em.flush();
		}
	}

	public void writeCyclesToDatabase() throws IOException {
		CycleBase[] sources = JsonUtils.read(CYCLE_FILE_NAME, CycleBase[].class);

		for (CycleBase source : sources) {
			CycleBase target = ccbDao.findUnique(source);
			if (target == null) {
				persist(source);
			} else {
				merge(target, source);
			}
			em.flush();
		}
	}

	public void writeCardSetsToDatabase() throws IOException {
		CardSetBase[] sources = JsonUtils.read(CARD_SET_FILE_NAME, CardSetBase[].class);

		for (CardSetBase source : sources) {
			log.info("writing: {}", source.getTechName());

			CardSetBase target = csbDao.findUnique(source);
			if (target == null) {
				target = source;
			}

			CycleBase cycleBase = source.getCycleBase();
			if (cycleBase != null) {
				cycleBase = ccbDao.findUnique(cycleBase);
				if (cycleBase == null) {
					throw new RuntimeException("Unable to find cycle for card set: " + source.getTechName());
				}
			}
			target.setCycleBase(cycleBase);

			if (target == source) {
				persist(target);
			} else {
				merge(target, source);
			}
			em.flush();
		}
	}

	public void writeCardsToDatabase() throws IOException {
		CardBase[] sources = JsonUtils.read(CARD_FILE_NAME, CardBase[].class);

		for (CardBase source : sources) {
			log.info("writing: {}", source.getTechName());

			CardBase target = cbDao.findUnique(source);
			if (target == null) {
				target = source;
			}

			// update warlord
			CardBase warlordBase = source.getWarlordBase();
			if (warlordBase != null) {
				warlordBase = cbDao.findUnique(warlordBase);
				if (warlordBase == null) {
					throw new RuntimeException("Unable to find warlord for card: " + source.getTechName());
				}
			}
			if (warlordBase != null) {
				target.setWarlordBase(warlordBase);
			}

			// update card set
			CardSetBase csb = source.getCardSetBase();
			if (csb != null) {
				csb = csbDao.findUnique(csb);
				if (csb == null) {
					throw new RuntimeException("Unable to find card set for card: " + source.getTechName());
				}
			}
			target.setCardSetBase(csb);

			if (target == source) {
				persist(target);
			} else {
				merge(target, source);
			}
			em.flush();
		}
	}

	public void writeDomainsToJsonFile(List<DomainBase> list, Processor<DomainBase> processor)
			throws JsonProcessingException, IOException {

		List<DomainBase> writeList;

		if (processor == null || disableProcessors) {
			writeList = list;
		} else {
			writeList = new ArrayList<>();
			for (DomainBase db : list) {
				writeList.add(processor.process(db));
			}
		}

		JsonUtils.write(writeList, DOMAIN_FILE_NAME);
	}

	public void writeCyclesToJsonFile(List<CycleBase> list, Processor<CycleBase> processor)
			throws JsonProcessingException, IOException {

		List<CycleBase> writeList;

		if (processor == null || disableProcessors) {
			writeList = list;
		} else {
			writeList = new ArrayList<>();
			for (CycleBase cb : list) {
				writeList.add(processor.process(cb));
			}
		}

		JsonUtils.write(writeList, CYCLE_FILE_NAME);
	}

	public void writeCardSetsToJsonFile(List<CardSetBase> list, Processor<CardSetBase> processor)
			throws JsonProcessingException, IOException {

		List<CardSetBase> writeList = new ArrayList<>();

		for (CardSetBase cardSetBase : list) {
			CycleBase cycleBase = cardSetBase.getCycleBase();
			if (cycleBase != null) {
				cycleBase = new CycleBase();
				cycleBase.setTechName(cardSetBase.getCycleBase().getTechName());
				cardSetBase.setCycleBase(cycleBase);
			}

		}

		if (processor == null || disableProcessors) {
			writeList = list;
		} else {
			for (CardSetBase cardSetBase : list) {
				writeList.add(processor.process(cardSetBase));
			}
		}

		JsonUtils.write(writeList, CARD_SET_FILE_NAME);
	}

	public void writeCardsToJsonFile(List<CardBase> list, Processor<CardBase> processor)
			throws JsonProcessingException, IOException {

		List<CardBase> writeList = new ArrayList<>();

		for (CardBase cardBase : list) {
			CardBase warlordBase = cardBase.getWarlordBase();
			if (warlordBase != null) {
				warlordBase = new CardBase();
				warlordBase.setTechName(cardBase.getWarlordBase().getTechName());
				cardBase.setWarlordBase(warlordBase);
			}
			CardSetBase cardSetBase = cardBase.getCardSetBase();
			if (cardSetBase != null) {
				cardSetBase = new CardSetBase();
				cardSetBase.setTechName(cardBase.getCardSetBase().getTechName());
				cardBase.setCardSetBase(cardSetBase);
			}
		}

		if (processor == null || disableProcessors) {
			writeList = list;
		} else {
			for (CardBase cardBase : list) {
				writeList.add(processor.process(cardBase));
			}
		}

		JsonUtils.write(writeList, CARD_FILE_NAME);
	}

	private <B extends IBase<L>, L extends ILang<B>> void persist(B target) {
		JpaUtils.smartPersist(em, target);
	}

	private <B extends IBase<L>, L extends ILang<B>> void merge(B target, B source) {
		JpaUtils.smartMerge(em, target, source, langItemsOnly);
	}
}
